[PHP] 上傳大頭照的功能


Posted by Eshau on 2021-06-16

前言

這幾天在實作留言板,因此想了很多可以加上去的功能,目前這個留言板有註冊、登入、留言等的基本功能,但看著那空無一物的大頭照,就決定來做個讓使用者上傳大頭照的功能。

目標

不想使用原生的 input 按鈕,希望使用者不需要提交就能在圖片上傳之後自動改變大頭照,而在提交之後使用者也可以自由更改大頭照。

改變原生按鈕

會想這樣做是因為看到很多網站,都有很漂亮的按鈕或是樣式包含了上傳圖片的功能,這邊我直接前往這些網站去看他們的程式碼,來了解他們是怎麼做到的,而我發現他們是把上傳圖片的 input 給藏起來,並使用 label 標籤的 for 屬性來使用上傳圖片的功能。

<form method="POST" class="board__upload" id="img-load" 
action="upload.php"  enctype="multipart/form-data">
  <input type="file" class="nodisplay" id="file" name="file" />
  <label for="file" class="upload-body">照片</label>
</form>

這邊是用 nodisplay 這個 class 來隱藏 input 按鈕,這樣就可以自己做一個喜歡的樣式來取代它,不過這邊會有個小問題,那就是只有點擊一部分的地方才可以觸發事件,這邊我認為可能是有東西被擋住了按鈕,因此我加了z-index:1;,就可以順利執行了。

上傳照片即跳轉

我們有一個可以上傳照片的按鈕後,接下來我們要讓表單在圖片上傳後自動跳轉至upload.php,這樣我們就可以對圖片進行分析及處理

document.getElementById('file').onchange = function() {
  document.getElementById('img-load').submit()
}

或是也可以在 input 內加入 onchange 屬性

<input type="file" id="file" class="nodisplay" name="file"
onchange="this.form.submit()" />

處理照片

這邊我們已經跳轉到了 upload.php,首先我們要確認照片是否上傳成功,可以參考 PHP Manual 來對各項錯誤進行判斷。

接下來我們來判斷上傳的檔案的類型,我們不希望使用者上傳「非圖片類型」的資料,因此我們這樣處理

$error = $_FILES['file']['error'];
if ($error === 0) {
  $tmpname = $_FILES['file']['tmp_name']; // 圖片暫存位置
  $username = $_SESSION['username'];
  $type = $_FILES['file']['type']; // 圖片類型
  switch($type) {
  case 'image/jpeg':
    $safeType = true;
    break;
  case 'image/gif':
    $safeType = true;
    break;
  case 'image/png':
    $safeType = true;
    break;
 }
 .
 .
}

接下來我們使用 base64_encode 將圖片轉換成文字檔

if ($safeType) {
    $file = fopen($tmpname, 'rb'); // 以二進位制開啟圖片
    $fileContent = fread($file, filesize($tmpname)); // 讀取文件
    fclose($file); // 關閉圖片
    $fileContent = base64_encode($fileContent);// 將圖片編碼成 Base64 文字
    .
    .
}

上傳至資料庫

這邊因為我們要將 base64 編碼的文件傳至資料庫,因此我們需要 4 個數值 idtypeimageusername,另外 image 必須為 blob 類型,這邊我是使用 longblob 作為圖片存至資料庫的類型。
Imgur

接下來我們做個判斷,判斷使用者是初次上傳照片還是要更換照片

$check = $conn->query("SELECT image FROM headshot WHERE username='$username'");
if ($check->num_rows === 1) {
  $sql = sprintf(
  "UPDATE headshot SET image='%s', type='%s' WHERE username='%s'",
  $fileContent,
  $type,
  $username     
  );
} else {
  $sql = sprintf(
  "INSERT INTO headshot(image, username, type)
    VALUES('%s', '%s', '%s')",
  $fileContent,
  $username,
  $type
  );
}
$result = $conn->query($sql);
header("Location: index.php");

這邊首先用 session 內的 username 判斷該使用者使否已上傳過大頭照,接著分別用 UPDATE 及 INSERT 來對資料庫進行操作,最後跳回首頁。

套用至討論區

現在狀況是討論區的各個使用者的訊息都已經顯示出來了,只差將照片依照不同使用者來套用至頭像,這邊我使用 nickname 去搜尋 username

(utils.php)

require_once('conn.php');
function getHeadShot($nickname) {
  global $conn;
  $result = '';
  $getUsername = $conn->query("SELECT username FROM users WHERE nickname='$nickname'");
  $username = $getUsername->fetch_assoc()['username'];
  $getImageInfo = $conn->query("SELECT image FROM image WHERE username='$username'");
  if ($getImageInfo->num_rows === 1) {
    $img = $getImageInfo->fetch_assoc()['image'];
    $type = $getImageInfo->fetch_assoc()['type'];
    $result = '"data:' . $type . ';base64,' . $img;
  }
  return $result;
}

$result 是個字串,為 Data URI 的格式,Data URI 的格式是 data:[<mediatype>][;base64],<data>。另外這邊考慮的點是如果使用者還未上傳照片,那就回傳空字串,接下我們就可以在「印出每個討論」的迴圈內把圖片一個一個套用至頭像

<?php
  $result = $conn->query("SELECT * FROM comments ORDER BY id DESC");
  while($row = $result->fetch_assoc()) {
    $haveHeadShot = getHeadShot($row['nickname']);
    if ($haveHeadShot) { 
      echo '<img class="comment__avatar" src=' . $haveHeadShot . '" />'; 
    } else { 
      echo '<div class="comment__avatar"></div>';
    }
  }
?>

如此我們就可以依照 haveHeadShot 是否有值來判斷是否使用資料庫照片或是預設,並把照片套用至各個使用者。










Related Posts

Linkedin Java 檢定題庫  try catch 流程

Linkedin Java 檢定題庫 try catch 流程

Print lots of stars patterns

Print lots of stars patterns

CH2. UML

CH2. UML


Comments